#include "Scene.h"
#include "ElementLoader.h"
#include "ConcreteElementLoader.h"
#include "Perspective.h"
#include "Ortho.h"
#include "Illumination.h"
#include "Spot.h"
#include "Component.h"
#include "ParsingErrorException.h"

#include "Graph.h"

using namespace std;

/*
	############################################################

	PARSING OF DSX FILE:
		-> Implement Exceptions to be thrown if xml file has errors;
			(bool ain't enough my friend)


*/

const string first_level_values[] = {
	GET_VALUE(Scene), GET_VALUE_S(View), GET_VALUE(Illumination), 
	GET_VALUE_S(Light), GET_VALUE_S(Texture), GET_VALUE_S(Material), 
	GET_VALUE_S(Transformation), GET_VALUE_S(Primitive), GET_VALUE_S(Component)
};

Component *Scene::rootComponent;

GLUquadric *Scene::glQ;

map<string, View *> Scene::views;
map<string, Light *> Scene::lights;
map<string, Material *> Scene::materials;
map<string, Texture *> Scene::textures;
map<string, Transformation *> Scene::transformations;
map<string, Primitive *> Scene::primitives;
map<string, Component *> Scene::components;

Scene::Scene(const char * _filename) : doc(_filename) {
	bool loadOkay = doc.LoadFile();

	if ( !loadOkay ) {
	  throw exception(); // Define exceptions? or just use bools
	  cout << "Error opening XML file" << endl;
	}

	cout << "Opened file" << endl;
	this->rootElement = doc.RootElement();
}

void Scene::initScene() {
	glQ = gluNewQuadric();
	/*
	loaders[GET_VALUE_S(Scene)] = Scene::loader;
	loaders[GET_VALUE_S(View)] = View::loader;
	loaders[GET_VALUE_S(Illumination)] = Illumination::loader;
	loaders[GET_VALUE_S(Light)] = Light::loader;
	loaders[GET_VALUE_S(Texture)] = Texture::loader;
	loaders[GET_VALUE_S(Material)] = Material::loader;
	loaders[GET_VALUE_S(Transformation)] = Transformation::loader;
	loaders[GET_VALUE_S(Primitive)] = Primitive::loader;
	loaders[GET_VALUE_S(Component)] = Component::loader;*/
}
#include <stack>
void Scene::displayScene() {
	stack<Material> stack_materials;

	rootComponent->displayElement();
}

void Scene::loadScene() {

	loadSceneProperties();

}

bool Scene::loadSceneProperties() {
	sceneElement = rootElement->FirstChildElement( first_level_values[0].c_str() );
	viewsElement = rootElement->FirstChildElement( first_level_values[1].c_str() );
	illuminationElement = rootElement->FirstChildElement( first_level_values[2].c_str() );
	lightsElement = rootElement->FirstChildElement( first_level_values[3].c_str() );
	texturesElement = rootElement->FirstChildElement( first_level_values[4].c_str() );
	materialsElement = rootElement->FirstChildElement( first_level_values[5].c_str() );
	transformationsElement = rootElement->FirstChildElement( first_level_values[6].c_str() );
	primitivesElement = rootElement->FirstChildElement( first_level_values[7].c_str() );
	componentsElement = rootElement->FirstChildElement( first_level_values[8].c_str() );

	#ifdef DEBUG
		for(int i = 0; i < 9; ++i) {
			cout << i << ": " << first_level_values[i] << endl;
		}
	#endif

	// Basic Scene information
	if (sceneElement == NULL) {
		cout << "Scene tag, not found\n";
		return ERROR;
	}
	else if(viewsElement == NULL) {
		cout << "Views tag, not found\n";
		return ERROR;
	}
	else if(illuminationElement == NULL) {
		cout << "Illumination tag, not found\n";
		return ERROR;
	}
	else if(lightsElement == NULL) {
		cout << "Lights tag, not found\n";
		return ERROR;
	}
	else if(texturesElement == NULL) {
		cout << "Textures tag, not found\n";
		return ERROR;
	}
	else if(materialsElement == NULL) {
		cout << "Materials tag, not found\n";
		return ERROR;
	}
	else if(transformationsElement == NULL) {
		cout << "Transformations tag, not found\n";
		return ERROR;
	}
	else if(primitivesElement == NULL) {
		cout << "Primitives tag, not found\n";
		return ERROR;
	}
	else if(componentsElement == NULL) {
		cout << "Components tag, not found\n";
		return ERROR;
	}
	else { 
		cout << "All basic elements were found! " << endl;
		return handleDocument();
	}

	return NO_ERROR;
}

bool Scene::handleDocument() {
	bool flag = 0;
	
	flag |= handleScene();
	flag |= handleViews();
	flag |= handleIllumination();
	flag |= handleLights();
	flag |= handleTextures();
	flag |= handleMaterials();
	flag |= handleTransformations();
	flag |= handlePrimitives();
	flag |= handleComponents();

	return flag;
}

bool Scene::handleScene() {
	// Scene attributes
	
	float axisLength = 0;
	root = (string) sceneElement->Attribute("root");
	
	#ifdef DEBUG
		cout << "handleScene()" <<endl;
		cout << "Value: " << sceneElement->Value() << endl;
		cout << "root=" << root << endl;
	#endif

	if (sceneElement->QueryFloatAttribute("length", &axisLength) == TIXML_SUCCESS) {
		#ifdef DEBUG
			cout << "<scene root=" << root << " axis length=" << axisLength << ">" << endl; 
		#endif
		axis_length = axisLength;
		return NO_ERROR; 
	}
	
	return ERROR;
}

bool Scene::handleViews() {

	// Views attributes
	float axisLength = 0;
	string atrDefault = string(viewsElement->Attribute("default"));

	#ifdef DEBUG
		cout << "<views default=" << atrDefault << ">" << endl;
	#endif
	
	for(TiXmlElement *child(viewsElement->FirstChildElement()); child != NULL; child = child->NextSiblingElement()) {

		const string namec(child->Value());
		const string id1(child->Attribute("id"));

		#ifdef DEBUG
		cout << id1 << endl;
		#endif

		if (namec == "perspective") {
			#ifdef DEBUG
				cout << "handling perspective view" << endl;
			#endif
			float near, far, angle;
			float xf, yf, zf;
			float xt, yt, zt;
			View::handleNFA(child, near, far, angle);
			View::handleVector(child->FirstChildElement("from"), xf, yf, zf);
			View::handleVector(child->FirstChildElement("to"), xt, yt, zt);
			#ifdef DEBUG
					cout << "near:" << near << " far:" << far << " angle:" << angle << endl; 
					cout << "x:" << xf << " y:" << yf << " z:" << zf << endl; 
					cout << "x:" << xt << " y:" << yt << " z:" << zt << endl;
			#endif

			sCoord3 from = {xf, yf, zf};
			sCoord3 to = {xt, yt, zt};
			Perspective perspective ( from, to, near, far, angle);
			// tree.add(perspective);

		}
		else if (namec == "ortho") {
			#ifdef DEBUG
				cout << "handling ortho view" << endl;
			#endif

			float near, far, left, right, top, bottom;
			View::handleNFF(child, near, far, left, right, top, bottom);

			#ifdef DEBUG
					cout << "near:" << near << " far:" << far << " left:" << left << endl; 
					cout << "right:" << right << " top:" << top << " bottom:" << bottom << endl; 
			#endif

			Ortho ortho (near, far, left, right, top, bottom);
			// tree.add(ortho);
		}
	}
	
	return NO_ERROR;
}

bool Scene::handleIllumination() {

	
	// Illumination attributes
	int doublesided, local;
	sRgba ambient, background;

	Illumination::handleProperties(illuminationElement, doublesided, local);
	Illumination::handleComponents(illuminationElement->FirstChildElement("ambient"), ambient);
	Illumination::handleComponents(illuminationElement->FirstChildElement("background"), background);
	
	#ifdef DEBUG
		cout << "<illumination doublesided=" << doublesided << " local=" << local << ">" << endl;
		cout << "r:" << ambient.r << " g:" << ambient.g << " b:" << ambient.b << " a:" << ambient.a << endl;
		cout << "r: " << background.r << " g:" << background.g << " b:" << background.b << " a:" << background.a << endl;
	#endif

	// Create illumination object
	// Add object to tree

	return ERROR;
}

bool Scene::handleLights() {

	for(TiXmlElement *child(lightsElement->FirstChildElement()); child != NULL; child = child->NextSiblingElement()) {
		if (string(child->Value()) == "omni") {

			int enabled;
			const string id1(child->Attribute("id"));
			sCoord4 location; // x,y,z,w
			sLightComponents lights;
			Light::handleProperties(child, enabled);
			Scene::handleVector(child->FirstChildElement("location"), location, 1);
			Illumination::handleComponents(child->FirstChildElement("ambient"), lights.ambient);
			Illumination::handleComponents(child->FirstChildElement("diffuse"), lights.diffuse);
			Illumination::handleComponents(child->FirstChildElement("specular"), lights.specular);

			#ifdef DEBUG
				cout << "omni" << endl;
				cout << "id: " << id1 << " enabled: " << enabled << endl;
				
				Scene::printVector(location, 1);
				Light::printComponents(lights);
			#endif

			// Omni omni(id, enabled, lights);
			// add to tree
		}
		else if(string(child->Value()) == "spot") { // spot

			int enabled;
			float angle, exponent;
			sCoord4 location, target;
			sLightComponents lights;
			const string id1(child->Attribute("id"));
			
			Spot::handleProperties(child, enabled, angle, exponent);
			Scene::handleVector(child->FirstChildElement("location"), location, 0);
			Scene::handleVector(child->FirstChildElement("target"), target, 0);
			Illumination::handleComponents(child->FirstChildElement("ambient"), lights.ambient);
			Illumination::handleComponents(child->FirstChildElement("diffuse"), lights.diffuse);
			Illumination::handleComponents(child->FirstChildElement("specular"), lights.specular);
			
			#ifdef DEBUG
				cout << "spot" << endl;
				cout << "id: " << id1 << " enabled: " << enabled << endl;
				cout << "angle: " << angle << " exponent: " << exponent << endl;
				
				cout << "location: "; Scene::printVector(location, 1);
				cout << "target: "; Scene::printVector(target, 1);
				Light::printComponents(lights);
			#endif 

			// Spot spot(id, enabled, lights, angle, exponent);
			// add to tree
		}
	}
	return ERROR;
}

// 1 means a 4 coords array
bool Scene::handleVector(TiXmlElement *element, sCoord4 &amb, int args) {

	if (element->QueryFloatAttribute("x", &amb.x) == TIXML_SUCCESS &&
			element->QueryFloatAttribute("y", &amb.y) == TIXML_SUCCESS &&
			element->QueryFloatAttribute("z", &amb.z) == TIXML_SUCCESS) {
	}
	else return ERROR;

	if (args) {
		if (!(element->QueryFloatAttribute("w", &amb.w) == TIXML_SUCCESS))
			return ERROR;
	}
	return NO_ERROR;
}


void Scene::printVector(sCoord4 &vec, int args) {

	cout << "x: " << vec.x << " y: " << vec.y << " z: " << vec.z;
	if (args) {cout << " w: " << vec.w;}
	cout << endl;
}

bool Scene::handleTextures() {

	for(TiXmlElement *child(texturesElement->FirstChildElement()); child != NULL; child = child->NextSiblingElement()) {

		Texture *tex = new Texture(child);
		textures[tex->id] = tex;

		#ifdef DEBUG
			tex->printTexture();
		#endif
	}
	
	return NO_ERROR;
}

bool Scene::handleMaterials() {

	for(TiXmlElement *child(materialsElement->FirstChildElement()); child != NULL; child = child->NextSiblingElement()) {
		try {
			Material *mat = new Material(child);
			materials[mat->id] = mat;

			#ifdef DEBUG
				mat->printMaterial();
			#endif
		} catch( ParsingErrorException *e ) {
			e->concatTag(child);
			throw e;
		}

	}
	
	return NO_ERROR;
}

bool Scene::handleTransformations() {
	for(TiXmlElement *child(transformationsElement->FirstChildElement()); child != NULL; child = child->NextSiblingElement()) {
		try {
			Transformation *transformation = new Transformation(child);
			transformations[transformation->id] = transformation;
		} catch(ParsingErrorException *e) {
			e->concatTag(child);
			throw e;
		}
	}

	return NO_ERROR;
}

bool Scene::handlePrimitives() {

	for(TiXmlElement *child(primitivesElement->FirstChildElement()); child != NULL; child = child->NextSiblingElement()) {

		try {
			Primitive *primitive = new Primitive(child);
			primitives[primitive->id] = primitive;
		} catch(ParsingErrorException *e) {
			e->concatTag(child);
			throw e;
		}
	}

	return NO_ERROR;
}

bool Scene::handleComponents() {

	for(TiXmlElement *child(componentsElement->FirstChildElement()); child != NULL; child = child->NextSiblingElement()) {
		try {
			Component *component = new Component(child);
			components[component->id] = component;
		} catch(ParsingErrorException *e) {
			e->concatTag(child);
			throw e;
		}
	}

	for(map<string, Component *>::iterator it = components.begin(); it != components.end(); ++it) {
		Component *c = components[it->first];
		for(vector<string>::iterator it2 = c->missing_childs.begin(); it2 != c->missing_childs.end(); ++it2)
			c->childs.push_back(components[*it2]);
	}

	rootComponent = components[root];

	return NO_ERROR;
}


void Scene::testTagValue(TiXmlElement *elem, string expected_value) {
	if( elem == NULL )
		throw new ParsingErrorException("missing tag. Expected Tags:\n " + expected_value + "\n");
	if(  ((string) elem->Value()) !=  expected_value )
		throw new ParsingErrorException((string) elem->Value() + ": invalid tag. " + expected_value + " tag expected.");
}

void Scene::testTagValue(TiXmlElement *elem, map<string, ElementLoader *> expected_values) {
	if( elem == NULL )
		throw new ParsingErrorException("missing tag.\n");
	if(  expected_values.count(elem->Value()) == 0 )
		throw new ParsingErrorException((string) elem->Value() + ": invalid tag.");
}

string Scene::testStringAttribute(TiXmlElement *elem, const char *atr) {
	if( elem == NULL )
		throw new ParsingErrorException("missing tag.");
	const char *atr_value = elem->Attribute(atr);
	if( atr_value == NULL )
		throw new ParsingErrorException(elem, (string) atr + " attribute missing");
	string _id(atr_value);

	return _id;
}

string Scene::testIdAttribute(TiXmlElement *elem, map<string, Primitive *> *primitives) {
	string id = testStringAttribute(elem, "id");

	if( primitives->count(id) == 0 )
		throw new ParsingErrorException(elem, "id=\"" + id + "\" unknown/invalid id");
	return id;
}

string Scene::testIdAttribute(TiXmlElement *elem, map<string, Component *> *components) {
	string id = testStringAttribute(elem, "id");

	if( components->count(id) == 0 )
		throw new ParsingErrorException(elem, "id=\"" + id + "\" unknown/invalid id");
	return id;
}

string Scene::testIdAttribute(TiXmlElement *elem, map<string, Texture *> *textures) {
	string id = testStringAttribute(elem, "id");

	if( textures->count(id) == 0 )
		throw new ParsingErrorException(elem, "id=\"" + id + "\" unknown/invalid id");
	return id;
}

string Scene::testIdAttribute(TiXmlElement *elem, map<string, Material *> *materials) {
	string id = testStringAttribute(elem, "id");

	if( materials->count(id) == 0 )
		throw new ParsingErrorException(elem, "id=\"" + id + "\" unknown/invalid id");
	return id;
}
